package rolling

import (
	"context"
	"fmt"
	"sync/atomic"
	"time"
)

type secondRollingWindow struct {
	buckets         []int64
	bucketsHeadTime int64
	size            int64
	expireCancel    context.CancelFunc
}

func NewSecondRollingWindow(period time.Duration) (RollingWindow, error) {
	if period < time.Second {
		return nil, fmt.Errorf("%w [ %v ]", ErrInvalidPeriod, period)
	}
	bucketSize := int64(period / time.Second)
	ctx, cancel := context.WithCancel(context.Background())
	rw := &secondRollingWindow{
		buckets:         make([]int64, bucketSize),
		bucketsHeadTime: time.Now().Unix(),
		size:            bucketSize,
		expireCancel:    cancel}

	exipreRoutine := func(ectx context.Context) {
		ticker := time.NewTicker(time.Second)
		counter := int64(0)
		expire := false
		for {
			counter++
			select {
			case <-ectx.Done():
				return
			case <-ticker.C:
				switch {
				case counter%bucketSize == 0:
					atomic.StoreInt64(&rw.bucketsHeadTime, time.Now().Unix())
					atomic.StoreInt64(&(rw.buckets[0]), 0)
					counter = 0
					expire = true
					continue
				case expire:
					atomic.StoreInt64(&(rw.buckets[counter]), 0)
				}
			}
		}
	}

	go exipreRoutine(ctx)

	return rw, nil
}

func (rw *secondRollingWindow) Add(num int64) {
	now := time.Now().Unix()
	pos := now - atomic.LoadInt64(&rw.bucketsHeadTime)

	//accident pos index protect
	if pos >= rw.size {
		pos = rw.size - 1
	}
	atomic.AddInt64(&(rw.buckets[pos]), num)
}

func (rw *secondRollingWindow) Sum() int64 {
	sumVal := int64(0)
	for i := int64(0); i < rw.size; i++ {
		sumVal += atomic.LoadInt64(&(rw.buckets[i]))
	}

	return sumVal
}

func (rw *secondRollingWindow) Max() int64 {
	max := int64(0)
	tmp := int64(0)
	for i := int64(0); i < rw.size; i++ {
		tmp = atomic.LoadInt64(&(rw.buckets[i]))
		if tmp > max {
			max = tmp
		}
	}
	return max
}

func (rw *secondRollingWindow) Avg() float64 {
	return float64(rw.Sum()) / float64(rw.size)
}

func (rw *secondRollingWindow) Stop() {
	rw.expireCancel()
}

func (rw *secondRollingWindow) String() string {
	msg := fmt.Sprintf("[")
	for i := int64(0); i < rw.size; i++ {
		msg = fmt.Sprintf("%s %d", msg, atomic.LoadInt64(&(rw.buckets[i])))
	}
	msg += " ]"

	return msg
}
